﻿using System;
using System.IO;
using System.Net;

namespace FatCatSniffer.Header
{
    public enum Protocol
    {
        TCP = 6,
        UDP = 17,
        Unknown = -1
    } ;

    public class IPHeader
    {
        //Eight bits for version and header length
        private readonly byte _byDifferentiatedServices;
        //Thirty two bit destination IP Address
        //End IP Header fields
        private readonly byte _byHeaderLength;
        //Header length
        private readonly byte[] _byIPData = new byte[4096];
        //Eight bits for TTL (Time To Live)
        private readonly byte _byProtocol;
        //Eight bits for flags and fragmentation offset
        private readonly byte _byTtl;
        //IP Header fields
        private readonly byte _byVersionAndHeaderLength;
        //Eight bits for the underlying protocol
        private readonly short _sChecksum;
        //Thirty two bit source IP Address
        private readonly uint _uiDestinationIPAddress;
        //Sixteen bits containing the checksum of the header
        //(checksum can be negative so taken as short)
        private readonly uint _uiSourceIPAddress;
        //Sixteen bits for identification
        private readonly ushort _usFlagsAndOffset;
        //Sixteen bits for total length of the datagram (header + message)
        private readonly ushort _usIdentification;
        //Eight bits for differentiated services (TOS)
        private readonly ushort _usTotalLength;
        //Data carried by the datagram
        public IPHeader(byte[] byBuffer, int nReceived)
        {
            try
            {
                //Create MemoryStream out of the received bytes
                var memoryStream = new MemoryStream(byBuffer, 0, nReceived);
                //Next we create a BinaryReader out of the MemoryStream
                var binaryReader = new BinaryReader(memoryStream);

                //The first eight bits of the IP header contain the version and
                //header length so we read them
                _byVersionAndHeaderLength = binaryReader.ReadByte();

                //The next eight bits contain the Differentiated services
                _byDifferentiatedServices = binaryReader.ReadByte();

                //Next eight bits hold the total length of the datagram
                _usTotalLength = (ushort) IPAddress.NetworkToHostOrder(binaryReader.ReadInt16());

                //Next sixteen have the identification bytes
                _usIdentification = (ushort) IPAddress.NetworkToHostOrder(binaryReader.ReadInt16());

                //Next sixteen bits contain the flags and fragmentation offset
                _usFlagsAndOffset = (ushort) IPAddress.NetworkToHostOrder(binaryReader.ReadInt16());

                //Next eight bits have the TTL value
                _byTtl = binaryReader.ReadByte();

                //Next eight represnts the protocol encapsulated in the datagram
                _byProtocol = binaryReader.ReadByte();

                //Next sixteen bits contain the checksum of the header
                _sChecksum = IPAddress.NetworkToHostOrder(binaryReader.ReadInt16());

                //Next thirty two bits have the source IP address
                _uiSourceIPAddress = (uint) (binaryReader.ReadInt32());

                //Next thirty two hold the destination IP address
                _uiDestinationIPAddress = (uint) (binaryReader.ReadInt32());

                //Now we calculate the header length

                _byHeaderLength = _byVersionAndHeaderLength;
                //The last four bits of the version and header length field contain the
                //header length, we perform some simple binary arithmetic operations to
                //extract them
                _byHeaderLength <<= 4;
                _byHeaderLength >>= 4;
                //Multiply by four to get the exact header length
                _byHeaderLength *= 4;

                //Copy the data carried by the data gram into another array so that
                //according to the protocol being carried in the IP datagram
                Array.Copy(byBuffer,
                           _byHeaderLength, //start copying from the end of the header
                           _byIPData, 0,
                           _usTotalLength - _byHeaderLength);
            }
            catch (Exception ex)
            {
                ex.PrintErrorToConsole();
            }
        }

        public string Checksum
        {
            get
            {
                //Returns the checksum in hexadecimal format
                return string.Format("0x{0:x2}", _sChecksum);
            }
        }

        public byte[] Data
        {
            get { return _byIPData; }
        }

        public IPAddress DestinationAddress
        {
            get { return new IPAddress(_uiDestinationIPAddress); }
        }

        public string DifferentiatedServices
        {
            get
            {
                //Returns the differentiated services in hexadecimal format
                return string.Format("0x{0:x2} ({1})", _byDifferentiatedServices,
                                     _byDifferentiatedServices);
            }
        }

        public string Flags
        {
            get
            {
                //The first three bits of the flags and fragmentation field 
                //represent the flags (which indicate whether the data is 
                //fragmented or not)
                int nFlags = _usFlagsAndOffset >> 13;

                if (nFlags == 2)
                {
                    return "Don't fragment";
                }

                if (nFlags == 1)
                {
                    return "More fragments to come";
                }

                return nFlags.ToString();
            }
        }

        public string FragmentationOffset
        {
            get
            {
                //The last thirteen bits of the flags and fragmentation field 
                //contain the fragmentation offset
                int nOffset = _usFlagsAndOffset << 3;
                nOffset >>= 3;

                return nOffset.ToString();
            }
        }

        public string HeaderLength
        {
            get { return _byHeaderLength.ToString(); }
        }

        public string Identification
        {
            get { return _usIdentification.ToString(); }
        }

        public ushort MessageLength
        {
            get
            {
                //MessageLength = Total length of the datagram - Header length
                return (ushort) (_usTotalLength - _byHeaderLength);
            }
        }

        public Protocol ProtocolType
        {
            get
            {
                //The protocol field represents the protocol in the data portion
                //of the datagram
                if (_byProtocol == 6) //A value of six represents the TCP protocol
                {
                    return Protocol.TCP;
                }

                return _byProtocol == 17 ? Protocol.UDP : Protocol.Unknown;
            }
        }

        public IPAddress SourceAddress
        {
            get { return new IPAddress(_uiSourceIPAddress); }
        }

        public string TotalLength
        {
            get { return _usTotalLength.ToString(); }
        }

        public string TTL
        {
            get { return _byTtl.ToString(); }
        }

        public string Version
        {
            get
            {
                //Calculate the IP version

                //The four bits of the IP header contain the IP version
                if ((_byVersionAndHeaderLength >> 4) == 4)
                {
                    return "IP v4";
                }

                return (_byVersionAndHeaderLength >> 4) == 6 ? "IP v6" : "Unknown";
            }
        }
    }
}